package com.voip.Invite;

import gov.nist.javax.sip.header.Route;
import gov.nist.javax.sip.stack.HopImpl;

import javax.sip.*;
import javax.sip.address.Hop;
import javax.sip.address.Router;
import javax.sip.address.URI;
import javax.sip.header.*;
import javax.sip.message.Request;
import javax.sip.message.Response;
import java.io.FileInputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.ParseException;
import java.util.*;

/**
 * Created by RXJ on 2018/1/24.
 */
public class SipHandler implements SipListener ,Router {

    SipStack sipStack;

    SipFactory sipFactory;

    SipProvider sipProvider;

    private String localHost ;

    private String remoteHost;

    private int localPort = 5060;

    private int remotePort = 5060;

    private static SipHandler instance;

    private Vector requestListeners, responseListeners, dtmfListeners;

    int sequenceNum;

    String registrarHost;
    int registrarPort ;
    String registrarDomain;
    String username;
    String password;

    String callID;

    boolean registered;

    static boolean initialized = false;

    public boolean isRegistered() {
        return registered;
    }

    public void setRegistered(boolean registered) {
        this.registered = registered;
    }



    public SipHandler(String remoteHost, int port) {
        sipStack = null;
        sipFactory = null;
        this.remoteHost = remoteHost;
        this.remotePort = port;
        requestListeners = new Vector();
        responseListeners = new Vector();
        dtmfListeners = new Vector();
        sequenceNum = 1;
        // get the local address
        try {
            localHost = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            System.out.println(e);
        }
    }

    public void hangup(String recipient) {
        try {
            sendRequest(recipient, Request.BYE, null, null);
        } catch (PeerUnavailableException e) {
            System.out.println(e);
        } catch (TransactionUnavailableException e) {
            System.out.println(e);
        } catch (ParseException e) {
            System.out.println(e);
        } catch (InvalidArgumentException e) {
            System.out.println(e);
        } catch (SipException e) {
            System.out.println(e);
        }
    }

    public void addRequestListener(RequestListener listener) {
        requestListeners.addElement(listener);
    }

    public void addResponseListener(ResponseListener listener) {
        responseListeners.addElement(listener);

    }

    public void init() {
        if (!initialized) {
            initialized = true;
            try {
                Properties props = new Properties();
                props.setProperty("javax.sip.STACK_NAME", "SipHandler");
                props.setProperty("javax.sip.IP_ADDRESS", localHost);
               // props.setProperty("javax.sip.ROUTER_PATH", "com.voip.Invite.SipHandler");

              //  props.setProperty("javax.sip.RETRANSMISSION_FILTER", "true");
              //  props.setProperty("gov.nist.javax.sip.MAX_MESSAGE_SIZE", "1048576");

                // Register
                if (getRegistrarHost() != null) {
                /*
                    props.setProperty("javax.sip.OUTBOUND_PROXY",
                            getRegistrarHost() + ":" + getRegistrarPort() + "/udp");
                    props.setProperty("net.java.sip.communicator.sip.DEFAULT_AUTHENTICATION_REALM",
                                    getRegistrarDomain());
                    props.setProperty("net.java.sip.communicator.sip.USER_NAME", getUsername());
                 */
                    setRegistered(false);
                } else {
                    setRegistered(true);
                }

                // Drop the client connection after we are done with the
                // transaction.
                props.setProperty(
                        "gov.nist.javax.sip.CACHE_CLIENT_CONNECTIONS", "false");
                // Set to 0 in your production code for max speed.
                // You need 16 for logging traces. 32 for debug + traces.
                // Your code will limp at 32 but it is best for debugging.
                props.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");
                props.setProperty("gov.nist.javax.sip.SERVER_LOG", "SipHandler.txt");
                props.setProperty("gov.nist.javax.sip.DEBUG_LOG", "SipHandler.log");

                sipFactory = SipFactory.getInstance();
                sipFactory.setPathName("gov.nist");

                sipStack = sipFactory.createSipStack(props);

                ListeningPoint udpListeningPoint = sipStack
                        .createListeningPoint(localPort, "udp");
                ListeningPoint tcpListeningPoint = sipStack
                        .createListeningPoint(localPort, "tcp");
                sipProvider = sipStack.createSipProvider(udpListeningPoint);
                sipProvider.addSipListener(this);
                sipProvider = sipStack.createSipProvider(tcpListeningPoint);
                sipProvider.addSipListener(this);

                // Setup callID
                callID = System.currentTimeMillis() + "@" + localHost;

                if (getRegistrarHost() != null) {
                    register();
                }
                while (!isRegistered()) {
                    Thread.sleep(100);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

        /**
         * 接受 request 请求处理
         * @param requestEvent
         */
    @Override
    public void processRequest(RequestEvent requestEvent) {
        Request request = requestEvent.getRequest();

        if (null == request) {
            System.out.println("processRequest request is null.");
            return;
        }
        if (Request.BYE.equalsIgnoreCase(request.getMethod())) {
            System.out.println("Request Bye :\n"+ request);
        }else{
            System.out.println(requestEvent);
        }

    }

    /**
     * 接受 response 回调处理
     * @param responseEvent
     */
    @Override
    public void processResponse(ResponseEvent responseEvent) {
        Response response = responseEvent.getResponse();

        String method = ((CSeqHeader) response.getHeader(CSeqHeader.NAME)).getMethod();

        if (response.getStatusCode() == Response.OK)  {
            if (method.equals(Request.REGISTER)) {
                setRegistered(true);
            }

        }

        if (response.getStatusCode() == 401) {
            handleAuthorization(responseEvent, response, method);
        } else if (response.getStatusCode() == 407) {
            handleProxyAuthorization(responseEvent, response, method);
        }

    }

    @Override
    public void processTimeout(TimeoutEvent timeoutEvent) {
        System.out.println("processTimeout:\n"+timeoutEvent);
    }

    @Override
    public void processIOException(IOExceptionEvent ioExceptionEvent) {
        System.out.println("processIOException:\n"+ioExceptionEvent);
    }

    @Override
    public void processTransactionTerminated(TransactionTerminatedEvent transactionTerminatedEvent) {
        System.out.println("processTransactionTerminated:\n"+transactionTerminatedEvent);
    }

    @Override
    public void processDialogTerminated(DialogTerminatedEvent dialogTerminatedEvent) {
        System.out.println("processDialogTerminated:\n"+dialogTerminatedEvent);
    }


    public SipProvider getSipProvider() {
        return sipProvider;
    }

    public void setSipProvider(SipProvider sipProvider){
        this.sipProvider = sipProvider;
    }

    public static SipHandler getInstance() {
        return instance;
    }

    public static SipHandler getInstance(String remoteHost, int port) {
        if (instance == null) {
            instance = new SipHandler(remoteHost, port);
        }

        return instance;
    }

    public String getRegistrarHost() {
        return registrarHost;
    }
    public void  setRegistrarHost(String registrarHost) {
         this.registrarHost =registrarHost;
    }

    public int getRegistrarPort() {
        return registrarPort;
    }
    public void setRegistrarPort(int port){
        this.registrarPort = port;
    }

    public int getLocalPort() {
        return localPort;
    }

    public String getLocalHost() {
        return localHost;
    }

    public String getRegistrarDomain() {
        return registrarDomain;
    }

    public void setRegistrarDomain(String registrarDomain) {
        this.registrarDomain = registrarDomain;
    }

    public String getUsername() {
        if (username == null) return "814";
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getCallID() {
        return callID;
    }

    public void setCallID(String callID) {
        this.callID = callID;
    }

    public int getRemotePort() {
        return remotePort;
    }

    public void setRemotePort(int remotePort) {
        this.remotePort = remotePort;
    }

    public String getRemoteHost() {
        return remoteHost;
    }

    public void setRemoteHost(String remoteHost) {
        this.remoteHost = remoteHost;
    }

    /**
     * 发送请求公共方法
     * @param recipient
     * @param type
     * @param content
     * @param contentType
     * @throws SipException
     * @throws ParseException
     * @throws InvalidArgumentException
     */
    public void sendRequest (String recipient, String type, String content, ContentTypeHeader contentType) throws SipException, ParseException , InvalidArgumentException {
        URI uri = sipFactory.createAddressFactory().createURI(recipient);
        Request request = sipFactory.createMessageFactory().createRequest(
                uri,
                type,
                sipFactory.createHeaderFactory().createCallIdHeader(getCallID()),
                sipFactory.createHeaderFactory().createCSeqHeader(sequenceNum++, type),
                sipFactory.createHeaderFactory().createFromHeader(
                        sipFactory.createAddressFactory().createAddress(
                                "sip:"+username+"@"
                                        + SipHandler.getInstance()
                                        .getLocalHost()
                                        + ":"
                                        + SipHandler.getInstance()
                                        .getLocalPort()),
                        Integer.toString(hashCode())),
                sipFactory.createHeaderFactory().createToHeader(
                        sipFactory.createAddressFactory().createAddress(
                                recipient), null),
                new ArrayList(),
                sipFactory.createHeaderFactory().createMaxForwardsHeader(10));

        ContactHeader contactHeader = sipFactory.createHeaderFactory()
                .createContactHeader(
                        sipFactory.createAddressFactory().createAddress(
                                "sip:"+username+"@"
                                        + SipHandler.getInstance()
                                        .getLocalHost()
                                        + ":"
                                        + SipHandler.getInstance()
                                        .getLocalPort()));
        request.addHeader(contactHeader);

        if (content != null) {
            request.setContent(content, contentType);
        }

        ClientTransaction ct = getSipProvider().getNewClientTransaction(request);
        ct.sendRequest();

    }

    /**
     * 注册方法
     * @throws PeerUnavailableException
     * @throws ParseException
     */
    public void register() throws PeerUnavailableException, ParseException {
        // Register with Registrar server
        if (registrarHost != null) {
            register(Request.REGISTER, "sip:"+registrarHost+":"+registrarPort+";transport=udp",
                    "sip:"+username+"@"+localHost+":"+localPort+";transport=udp", null,
                    sipFactory.createHeaderFactory().createCallIdHeader(callID));
        }
    }

    /**
     * 注册请求方法
     * @param type
     * @param registrarURI
     * @param to
     * @param authnHeader
     * @param callID
     */
    public void register(String type, String registrarURI, String to, AuthorizationHeader authnHeader, CallIdHeader callID) {
        try {
            Request registerRequest = sipFactory
                    .createMessageFactory()
                    .createRequest(
                            sipFactory.createAddressFactory().createURI(registrarURI),
                            type,callID,
                            sipFactory.createHeaderFactory().createCSeqHeader(
                                    sequenceNum++, type),
                            sipFactory
                                    .createHeaderFactory()
                                    .createFromHeader(
                                            sipFactory
                                                    .createAddressFactory()
                                                    .createAddress("sip:"+getUsername()+"@"+localHost+":"+localPort+";transport=udp"),
                                            Integer.toString(hashCode())),
                            sipFactory
                                    .createHeaderFactory()
                                    .createToHeader(
                                            sipFactory
                                                    .createAddressFactory()
                                                    .createAddress(to),
                                            null),
                            new ArrayList(),
                            sipFactory.createHeaderFactory()
                                    .createMaxForwardsHeader(10));

            ContactHeader contactHeader = sipFactory.createHeaderFactory()
                    .createContactHeader(
                            sipFactory.createAddressFactory().createAddress(
                                    "sip:"+getUsername()+"@"
                                            + localHost
                                            + ":"
                                            + localPort));
            registerRequest.addHeader(contactHeader);

            if (authnHeader != null){
                registerRequest.addHeader(authnHeader);
            }

              ViaHeader  viaHeader = sipFactory.createHeaderFactory().createViaHeader(
                        localHost, localPort, "udp", null);
                registerRequest.addHeader(viaHeader);

            System.out.println("send request");
           /*
            ClientTransaction ct = getSipProvider().getNewClientTransaction(
                    registerRequest);
            ct.sendRequest();
             */
           getSipProvider().sendRequest(registerRequest);
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    /**
     * 注册401 处理 密码验证方法
     * @param responseEvent
     * @param response
     * @param method
     */
    private void handleAuthorization(ResponseEvent responseEvent, Response response, String method) {
        // Handle Unauthorized
        Header authenticateHeader = response.getHeader("WWW-Authenticate");
        if (authenticateHeader instanceof WWWAuthenticateHeader) {
            try {
                Request registerRequest = responseEvent.getClientTransaction().getRequest();
                WWWAuthenticateHeader wwwAuthnHeader = (WWWAuthenticateHeader)authenticateHeader;

                String digest = MessageDigestAlgorithm.calculateResponse
                        (wwwAuthnHeader.getAlgorithm(),
                                getUsername(), wwwAuthnHeader.getRealm(), password,
                                wwwAuthnHeader.getNonce(),
                                null, null, method,
                                registerRequest.getRequestURI().toString(),
                                registerRequest.getContent()==null?"":registerRequest.getContent().toString(),
                                wwwAuthnHeader.getQop());

                AuthorizationHeader authorization = sipFactory.createHeaderFactory().createAuthorizationHeader(wwwAuthnHeader.getScheme());

                authorization.setUsername(getUsername());
                authorization.setRealm(wwwAuthnHeader.getRealm());
                authorization.setNonce(wwwAuthnHeader.getNonce());
                authorization.setParameter("uri",registerRequest.getRequestURI().toString());
                authorization.setResponse(digest);
                if(wwwAuthnHeader.getAlgorithm() != null)
                    authorization.setAlgorithm(wwwAuthnHeader.getAlgorithm());
                if(wwwAuthnHeader.getOpaque() != null)
                    authorization.setOpaque(wwwAuthnHeader.getOpaque());

                register (registerRequest.getMethod(), "sip:"+username+"@"+registrarHost+":"+registrarPort+";transport=udp",
                        "sip:"+getUsername()+"@"+localHost+":"+localPort+";transport=udp", authorization,
                        (CallIdHeader)registerRequest.getHeader(CallIdHeader.NAME));
            } catch (PeerUnavailableException e) {
                System.out.println(e);
            } catch (ParseException e) {
                System.out.println(e);
            }
        }
    }

    /**
     * 407 验证处理
     * @param responseEvent
     * @param response
     * @param method
     */
    private void handleProxyAuthorization(ResponseEvent responseEvent, Response response, String method) {
        // Handle Unauthorized
        Header authenticateHeader = response.getHeader("Proxy-Authenticate");
        if (authenticateHeader instanceof ProxyAuthenticateHeader) {
            try {
                Request registerRequest = responseEvent.getClientTransaction().getRequest();
                ProxyAuthenticateHeader wwwAuthnHeader = (ProxyAuthenticateHeader)authenticateHeader;

                String digest = MessageDigestAlgorithm.calculateResponse
                        (wwwAuthnHeader.getAlgorithm(),
                                getUsername(), wwwAuthnHeader.getRealm(), password,
                                wwwAuthnHeader.getNonce(),
                                null, null, method,
                                registerRequest.getRequestURI().toString(),
                                registerRequest.getContent()==null?"":registerRequest.getContent().toString(),
                                wwwAuthnHeader.getQop());

                AuthorizationHeader authorization = sipFactory.createHeaderFactory().createProxyAuthorizationHeader(wwwAuthnHeader.getScheme());

                authorization.setUsername(getUsername());
                authorization.setRealm(wwwAuthnHeader.getRealm());
                authorization.setNonce(wwwAuthnHeader.getNonce());
                authorization.setParameter("uri",registerRequest.getRequestURI().toString());
                authorization.setResponse(digest);
                if(wwwAuthnHeader.getAlgorithm() != null)
                    authorization.setAlgorithm(wwwAuthnHeader.getAlgorithm());
                if(wwwAuthnHeader.getOpaque() != null)
                    authorization.setOpaque(wwwAuthnHeader.getOpaque());

                register (registerRequest.getMethod(), ((ToHeader)registerRequest.getHeader("to")).getAddress().toString(),
                        ((ToHeader)registerRequest.getHeader("to")).getAddress().toString(), authorization,
                        (CallIdHeader)registerRequest.getHeader(CallIdHeader.NAME));
            } catch (PeerUnavailableException e) {
                System.out.println(e);
            } catch (ParseException e) {
                System.out.println(e);
            }
        }
    }


    public static void main(String args[]) throws Exception {

        SipHandler handler = SipHandler.getInstance();

    }

    @Override
    public Hop getOutboundProxy() {
        return null;
    }

    @Override
    public ListIterator getNextHops(Request request) {
        LinkedList ll = new LinkedList();
        String remoteHost = getInstance().getRemoteHost();
        int port = getInstance().getRemotePort();
        ll.add(new HopImpli(remoteHost + ":" + port + "/udp"));
        return ll.listIterator();
    }

    @Override
    public Hop getNextHop(Request request) throws SipException {
        return null;
    }

    public void addDTMFListener(DTMFListener listener) {
        dtmfListeners.addElement(listener);
    }
}
